package com.medusa.aps.business.modules.basic.mp.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.exception.BusinessErrorCode;
import com.medusa.aps.business.model.pagination.Page;
import com.medusa.aps.business.modules.basic.model.WorkPeriod;
import com.medusa.aps.business.modules.basic.model.WorkingPeriodModel;
import com.medusa.aps.business.modules.basic.model.input.WorkingPeriodPagePram;
import com.medusa.aps.business.modules.basic.mp.entity.ClassSystem;
import com.medusa.aps.business.modules.basic.mp.entity.WorkingCalendar;
import com.medusa.aps.business.modules.basic.mp.entity.WorkingPeriod;
import com.medusa.aps.business.modules.basic.mp.mapper.WorkingCalendarMapper;
import com.medusa.aps.business.modules.basic.mp.mapper.WorkingPeriodMapper;
import com.medusa.aps.business.modules.basic.mp.service.ClassSystemService;
import com.medusa.aps.business.modules.basic.mp.service.WorkingPeriodService;
import com.medusa.aps.business.modules.plan.mp.service.ProcessDetailsService;
import com.medusa.aps.business.util.PercentageScheduleUtils;
import com.medusa.aps.business.common.redis.annotation.Redisson;
import com.medusa.aps.business.global.model.exception.GlobalException;
import com.medusa.aps.business.global.model.exception.collector.CurrentErrors;
import com.medusa.aps.business.global.model.exception.collector.DistinctValidator;
import com.medusa.aps.business.global.model.exception.collector.ErrorCollector;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 工作时段服务实现类
 *
 * @author yuxh
 * @since 2023-08-14 17:37:53
 */
@Service("workingPeriodService")
public class WorkingPeriodServiceImpl extends ServiceImpl<WorkingPeriodMapper, WorkingPeriod> implements WorkingPeriodService {

    @Resource
    private WorkingCalendarMapper workingCalendarMapper;
    @Resource
    private ProcessDetailsService processDetailsService;
    @Resource
    private ClassSystemService classSystemService;

    @Override
    public Page<WorkingPeriodModel> periodPage(WorkingPeriodPagePram page) {
        //分页查询工作时段配置
        Page<WorkingPeriod> result = this.lambdaQuery()
                .like(StrUtil.isNotBlank(page.getName()), WorkingPeriod::getWorkTimeName, page.getName())
                .orderByDesc(WorkingPeriod::getCreateTime)
                .page(page);
        //转换为工作时段模型
        return result
                .convert(
                        record -> new WorkingPeriodModel()
                                .setId(record.getId())
                                .setName(record.getWorkTimeName())
                                //是否可编辑
                                .setUnEditable(this.isPeriodExistProcessPlan(Set.of(record.getId())))
                                .periods(WorkPeriod.str2Periods(record.getStartAndEndTimeIntervalSet()))
                );
    }

    /**
     * 获取工作时段对应的正在生效的日历对应的设备id集合
     *
     * @param periodIds 工作时段id集合
     * @return 设备id集合
     */
    private Set<Long> calendarEquipmentIds(Set<Integer> periodIds) {
        List<WorkingCalendar> calendars = workingCalendarMapper.selectList(
                Wrappers.lambdaQuery(WorkingCalendar.class)
                        .select(WorkingCalendar::getDeviceId)
                        .in(WorkingCalendar::getWorkingTimeId, periodIds)
                        .ge(WorkingCalendar::getDate, LocalDate.now())
                        .groupBy(WorkingCalendar::getDeviceId)
        );
        if (CollUtil.isEmpty(calendars)) {
            return Set.of();
        }
        return calendars.stream().map(WorkingCalendar::getDeviceId).collect(Collectors.toSet());
    }

    /**
     * @param periodIds 工作时段id集合
     * @return 是否存在已经生成工序计划的设备
     */
    private boolean isPeriodExistProcessPlan(Set<Integer> periodIds) {
        Set<Long> equipmentIds = this.calendarEquipmentIds(periodIds);
        if (CollUtil.isEmpty(equipmentIds)) {
            return false;

        }
        return processDetailsService.isExistProcessPlan(
                equipmentIds,
                LocalDateTime.now()
        );
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.CLASS_SYSTEM_LOCK_KEY, waitTime = 1)
    public void saveWorkingPeriod(List<WorkingPeriodModel> periodModels) {
        //校验时段名称是否重复
        ErrorCollector errors = DistinctValidator.valid(periodModels, WorkingPeriodModel::getName, "工作时段名称");
        if (errors.hasError()) {
            throw new GlobalException(errors.toString());
        }
        ErrorCollector errorCollector = ErrorCollector.create();
        List<WorkingPeriod> saveOrUpdatePeriods = workingPeriodsDataValid(periodModels, errorCollector);
        if (errorCollector.hasError()) {
            throw new GlobalException(errorCollector.toString());
        }
        //批量保存或更新工作时段
        this.saveBatch(saveOrUpdatePeriods);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.CLASS_SYSTEM_LOCK_KEY, waitTime = 1)
    public void updateWorkingPeriod(List<WorkingPeriodModel> periodModels) {
        PercentageScheduleUtils.validClassSystemGenerateCalendar();
        //校验时段名称是否重复
        ErrorCollector errors = DistinctValidator.valid(periodModels, WorkingPeriodModel::getName, "工作时段名称");
        if (errors.hasError()) {
            throw new GlobalException(errors.toString());
        }
        //已经生成工作日历的工作时段即正在使用中的工作时段不允许编辑，需要该工作时段的设备被释放掉，没有被选用才可以编辑。
        //获取所有的需要编辑的时段 id 集合
        Set<Integer> editPeriodIds = periodModels.stream().filter(Objects::nonNull).map(WorkingPeriodModel::getId).collect(Collectors.toSet());
        if (CollUtil.isNotEmpty(editPeriodIds)) {
            if (isPeriodExistProcessPlan(editPeriodIds)) {
                throw new GlobalException("包含已生成工序计划的设备，不可修改。");
            }
        }
        ErrorCollector errorCollector = ErrorCollector.create();
        List<WorkingPeriod> saveOrUpdatePeriods = workingPeriodsDataValid(periodModels, errorCollector);
        if (errorCollector.hasError()) {
            throw new GlobalException(errorCollector.toString());
        }
        Map<Integer, WorkingPeriodModel> periodModelMap = this.lambdaQuery()
                .select(WorkingPeriod::getId, WorkingPeriod::getWorkTimeName, WorkingPeriod::getStartAndEndTimeIntervalSet)
                .in(WorkingPeriod::getId, editPeriodIds)
                .list()
                .stream()
                .map(item ->
                        new WorkingPeriodModel()
                                .setId(item.getId())
                                .setName(item.getWorkTimeName())
                                .periods(WorkPeriod.str2Periods(item.getStartAndEndTimeIntervalSet()))
                ).collect(Collectors.toMap(
                        WorkingPeriodModel::getId,
                        Function.identity()));
        this.updateBatchById(saveOrUpdatePeriods);
        //更新工作日历
        LocalDate now = LocalDate.now();
        periodModels.forEach(
                period -> {
                    Integer periodId = period.getId();
                    if (periodId == null) {
                        return;
                    }
                    workingCalendarMapper.update(
                            null,
                            Wrappers.lambdaUpdate(WorkingCalendar.class)
                                    .set(
                                            WorkingCalendar::getPeriodInfo,
                                            JSON.toJSONString(
                                                    new WorkingCalendar.WorkingTimeInfo().setName(period.getName()).setTimes(period.periods())
                                            )
                                    )
                                    .eq(WorkingCalendar::getWorkingTimeId, periodId)
                                    .ge(WorkingCalendar::getDate, now)
                    );

                    WorkingPeriodModel pre = periodModelMap.getOrDefault(periodId, new WorkingPeriodModel());
                }
        );
    }

    /**
     * 工作时段校验
     *
     * @param periodModels   工作时段原始数据
     * @param errorCollector 校验参数
     * @return 工作时段信息
     */
    private List<WorkingPeriod> workingPeriodsDataValid(List<WorkingPeriodModel> periodModels, ErrorCollector errorCollector) {
        //用于校验名称是否重复
        Set<String> names = periodModels.stream().map(WorkingPeriodModel::getName).collect(Collectors.toSet());
        Map<String, Set<Integer>> nameIdsMap = this.lambdaQuery()
                .select(WorkingPeriod::getId, WorkingPeriod::getWorkTimeName)
                .in(WorkingPeriod::getWorkTimeName, names)
                .list()
                .stream()
                .collect(Collectors.groupingBy(WorkingPeriod::getWorkTimeName, Collectors.mapping(WorkingPeriod::getId, Collectors.toSet())));
        //model 转 entity
        return periodModels.stream()
                .map(
                        model -> errorCollector.current(model.getName(),
                                () -> {
                                    try {
                                        model.valid();
                                    } catch (GlobalException bex) {
                                        CurrentErrors.add(bex.getMessage());
                                    }
                                    String name = model.getName();
                                    //校验名称是否重复
                                    Integer currModelId = model.getId();
                                    Set<Integer> sameNameIds = nameIdsMap.get(name);
                                    if (sameNameIds != null && !sameNameIds.contains(currModelId)) {
                                        CurrentErrors.add("名称与其它时段重复");
                                        return null;
                                    }
                                    return new WorkingPeriod()
                                            .setId(currModelId)
                                            .setWorkTimeName(name)
                                            .setStartAndEndTimeIntervalSet(
                                                    WorkPeriod.periods2Str(model.periods())
                                            );
                                })
                ).toList();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @Redisson(name = RedisConstants.CLASS_SYSTEM_LOCK_KEY, waitTime = 1)
    public void deleteBatch(Set<Integer> periodIds) {
        PercentageScheduleUtils.validClassSystemGenerateCalendar();
        //已生成工序计划的设备不允许删除
        if (isPeriodExistProcessPlan(periodIds)) {
            throw new GlobalException("包含已生成工序计划的设备，不可修改。");
        }
        List<WorkingPeriod> periods = this.lambdaQuery()
                .select(WorkingPeriod::getId, WorkingPeriod::getWorkTimeName)
                .in(WorkingPeriod::getId, periodIds)
                .list();
        if (CollUtil.isEmpty(periods) || periodIds.size() != periods.size()) {
            throw new GlobalException(BusinessErrorCode.DATA_IS_EMPTY.getCode(), "停止非法攻击");
        }

        //需要删除绑定的班制和工作日历
        LambdaQueryChainWrapper<ClassSystem> query = classSystemService.lambdaQuery()
                .select(ClassSystem::getId, ClassSystem::getPeriodIds);
        periodIds.forEach(periodId -> query.or().apply("JSON_CONTAINS(period_ids,JSON_ARRAY({0}))", periodId));
        //查询出班制 id 集合
        Map<Integer, List<Integer>> classIdPeriodIdsMap = query.list()
                .stream()
                .collect(Collectors.toMap(ClassSystem::getId, ClassSystem::getPeriodIds));
        if (CollUtil.isEmpty(classIdPeriodIdsMap)) {
            this.removeBatchByIds(periodIds);
            return;
        }
        //班制完全使用同一个时段 id 或所有时段id都处于需要被删除的列表 则完全删除班制和工作日历
        Set<Integer> removeClassIds = classIdPeriodIdsMap.entrySet()
                .stream()
                .filter(entry ->
                        //对应时段为空
                        CollUtil.isEmpty(entry.getValue())
                                //时段 id 都是同一个
                                || entry.getValue().stream().allMatch(id -> entry.getValue().get(0).equals(id))
                                //除休息外 所有时段 id 都处于需要删除列表
                                || periodIds.containsAll(entry.getValue().stream().filter(id -> id != 0).toList())
                )
                .map(Map.Entry::getKey).collect(Collectors.toSet());
        if (CollUtil.isNotEmpty(removeClassIds)) {
            removeClassIds.forEach(classIdPeriodIdsMap::remove);
            //删除班制
            classSystemService.removeBatchByIds(removeClassIds);
            //根据班制id 删除工作日历
            workingCalendarMapper.delete(
                    Wrappers.lambdaQuery(WorkingCalendar.class)
                            .in(WorkingCalendar::getClassId, removeClassIds)
                            .ge(WorkingCalendar::getDate, LocalDate.now())
            );
        }
        if (CollUtil.isEmpty(classIdPeriodIdsMap)) {
            this.removeBatchByIds(periodIds);
            return;
        }
        //其他情况需要把班制和日历的对应时段调整为休息
        Set<Integer> updateToRestPeriodIds = new HashSet<>();
        classIdPeriodIdsMap.forEach(
                (key, value) -> {
                    for (int index = 0; index < value.size(); index++) {
                        Integer current = value.get(index);
                        if (periodIds.contains(current)) {
                            value.set(index, 0);
                            updateToRestPeriodIds.add(current);
                        }
                    }
                    classSystemService.lambdaUpdate()
                            .set(ClassSystem::getPeriodIds, JSONUtil.toJsonStr(value))
                            .eq(ClassSystem::getId, key)
                            .update();
                }
        );
        workingCalendarMapper.update(
                null,
                Wrappers.lambdaUpdate(WorkingCalendar.class)
                        .set(WorkingCalendar::getWorkingTimeId, 0)
                        .set(WorkingCalendar::getPeriodInfo, null)
                        .in(WorkingCalendar::getWorkingTimeId, updateToRestPeriodIds)
        );
        this.removeBatchByIds(periodIds);
    }
}

